package cm

import (
	"math/big"
	"time"

	"gitee.com/law-api/log"
	apiTypes "gitee.com/law-api/types"

	"github.com/PicoChain/accounts/abi/bind"
	"github.com/PicoChain/crypto"
	"github.com/PicoChain/txpool"
	"github.com/PicoChain/types"
)

// Upload collect the key message to post to blockchain network.If txs is recieved,it start a
// timeout-rountine to listen whether its status was blocked.It will return error when timeout
// has arrived.
func (cm *ClientManager) Upload(hexkey string, data *apiTypes.ProofData, timeout int) (*apiTypes.ChainStatus, error) {
	//load user
	key, err := crypto.HexToECDSA(hexkey)
	user := bind.NewKeyedTransactor(key)

	//upload data
	tx, err := cm.uploadData(user, data)
	if err != nil {
		log.Warn("upload Data", "err", err)
	}
	switch err {
	case nil:
	case txpool.ErrInsufficientFunds:
		return nil, ErrAccGas
	case txpool.ErrNonceTooLow, txpool.ErrReplaceUnderpriced:
		return nil, ErrAccNonce
	default:
		//maybe fileid have already exit
		if cm.curContracter().checkExiting(data.FileID()) {
			return nil, ErrTxExisting
		}
		return nil, ErrNetwork
	}

	//wait mining
	return cm.waitResult(tx, data.FileID(), timeout)
}
func (cm *ClientManager) uploadData(user *bind.TransactOpts, dat *apiTypes.ProofData) (*types.Transaction, error) {
	cm.uploadLock.Lock()
	defer cm.uploadLock.Unlock()

	//set nonce, avioding nonce is conflict.
	nonce, err := cm.GetUserNonce(user.From)
	if err != nil {
		log.Warn("getNonce", "user", user.From.Hex(), "err", err)
	}
	user.Nonce = new(big.Int).SetUint64(nonce)
	//set higher gasPrice
	user.GasPrice = new(big.Int).SetUint64(1e9)

	//transact contract to  the blockchain.
	tx, err := cm.curContracter().addProofData(user, user.From.Hex(),
		dat.FileID(), dat.FileHash(), dat.Sm3(), dat.Md5(), dat.Timestamp())

	if err != nil {
		log.Warn("add tx", "fileid", dat.FileID(), "err", err.Error())
		return nil, err
	}

	//update user nonce
	cm.SetUserNonce(user.From, nonce+1)

	return tx, nil
}
func (cm *ClientManager) waitResult(tx *types.Transaction, fileid string, timeout int) (*apiTypes.ChainStatus, error) {
	//choose a random client to read status
	//notes: get transaction status from which notifies block successfully
	_, c := cm.randomClient()

	//wait result ,timeout: 3min *timeout.
	log.Info("waitMined", "listen node", c.RawUrl(), "target", tx.Hash().Hex(),
		"timeout(min)", timeout*3)

	failed := 0
	for i := 0; i < timeout; i++ {
		if err := cm.waitMined(c, tx, 3*time.Minute); err != nil {
			failed++
			time.Sleep(1 * time.Second)
			continue
		}
		break
	}
	//if timeout, make sure transaction in pending pool
	if failed >= timeout {
		log.Warn("timeout", "fileid", fileid)
		if tx, _ := cm.getTransactionByHash(c, tx.Hash()); tx != nil {
			return nil, ErrTxTimeout
		}
		return nil, ErrTxMissing
	}

	//check contract execute result
	if succeed, err := cm.checkContractStatus(c, tx.Hash()); err != nil || succeed != true {
		log.Warn("execute contract failed", "fileid", fileid)
		if cm.curContracter().checkExiting(fileid) {
			return nil, ErrTxExisting
		}
		return nil, ErrTxInvalid
	}

	//lookup txs index from blockchain
	block, err := cm.getBlockByTxHash(c, tx.Hash())
	if err != nil {
		log.Warn("unkonw chain status", "fileid", fileid, "err", err.Error())
		return nil, ErrTxPending
	}
	number := block.NumberU64()
	hash := block.Hash()
	t := block.Time().Uint64()

	//pack result
	res := apiTypes.NewChainStatus(number, hash, tx.Hash(), t)
	log.Info("upload succeed", "fileid", fileid)

	return res, nil
}
